# -*- coding: utf-8 -*-
import ctypes
from functools import wraps
from json import loads, dumps
lambda: "By Zero123"

class _CTypeCast:
    class PyRef:
        __slots__ = ("value",)
        def __init__(self):
            self.value = None
    
    def getRestype(self):
        pass

    def pyToC(self, pyObj):
        pass

    def cToPy(self, cObj):
        pass

    def resCallBack(self, ref: PyRef):
        pass

class ArrayStruct(ctypes.Structure):
    _fields_ = [
        ("head", ctypes.c_void_p),
        ("size", ctypes.c_int)
    ]

class CFloat(_CTypeCast):
    def pyToC(self, pyObj: float) -> ctypes.c_float:
        return ctypes.c_float(pyObj)

    def cToPy(self, cObj: ctypes.c_float) -> float:
        return cObj
    
    def getRestype(self):
        return ctypes.c_float

class CInt(_CTypeCast):
    def pyToC(self, pyObj: int) -> ctypes.c_int:
        return ctypes.c_int(pyObj)

    def cToPy(self, cObj: ctypes.c_int) -> int:
        return cObj

    def getRestype(self):
        return ctypes.c_int

class CDouble(_CTypeCast):
    def pyToC(self, pyObj: float) -> ctypes.c_double:
        return ctypes.c_double(pyObj)

    def cToPy(self, cObj: ctypes.c_double) -> float:
        return cObj

    def getRestype(self):
        return ctypes.c_double

class CLongLong(_CTypeCast):
    def pyToC(self, pyObj: int) -> ctypes.c_longlong:
        return ctypes.c_longlong(pyObj)

    def cToPy(self, cObj: ctypes.c_longlong) -> int:
        return cObj

    def getRestype(self):
        return ctypes.c_longlong

class CString(_CTypeCast):
    def pyToC(self, pyObj: str) -> ctypes.c_char_p:
        return ctypes.c_char_p(pyObj.encode())

    def resCallBack(self, ref):
        @ctypes.CFUNCTYPE(None, ctypes.c_char_p)
        def callBack(v: bytes):
            ref.value = v.decode()
        return callBack

def CREATE_VOID_POINTER_ARRAY(n):
    """ 分配N个void*组成的指针数组 """
    return (ctypes.c_void_p * n)()

class CStringArray(_CTypeCast):
    def pyToC(self, pyObj: list) -> ArrayStruct:
        return CStringArray.createArrayStruct(pyObj)

    @staticmethod
    def createArrayStruct(strList: list) -> ArrayStruct:
        pointerArray = CREATE_VOID_POINTER_ARRAY(len(strList))
        for i, s in enumerate(strList):
            encodedStr = s.encode()
            pointerArray[i] = ctypes.cast(ctypes.c_char_p(encodedStr), ctypes.c_void_p)  # 存储指针
        # 创建结构体并填充数据
        arrayStruct = ArrayStruct()
        arrayStruct.head = ctypes.cast(pointerArray, ctypes.c_void_p)
        arrayStruct.size = len(strList)
        return arrayStruct

    def resCallBack(self, ref):
        dataList = []
        @ctypes.CFUNCTYPE(None, ctypes.c_char_p)
        def callBack(v: bytes):
            dataList.append(v.decode())
        ref.value = dataList
        return callBack

class CBool(_CTypeCast):
    def pyToC(self, pyObj: bool) -> ctypes.c_bool:
        return ctypes.c_bool(pyObj)

    def cToPy(self, cObj: ctypes.c_bool) -> bool:
        return cObj

    def getRestype(self):
        return ctypes.c_bool

class CJson(_CTypeCast):
    def pyToC(self, pyObj) -> ctypes.c_char_p:
        if not isinstance(pyObj, (dict, list)):
            raise TypeError("{} 不是有效的数据类型".format(type(pyObj)))
        return ctypes.c_char_p(dumps(pyObj).encode())

    def resCallBack(self, ref):
        @ctypes.CFUNCTYPE(None, ctypes.c_char_p)
        def callBack(v: bytes):
            ref.value = loads(v.decode())
        return callBack

class C_CastType:
    cFloat = CFloat()
    cDouble = CDouble()
    cInt = CInt()
    cLongLong = CLongLong()
    cString = CString()
    cStringArray = CStringArray()
    cBool = CBool()
    cJson = CJson()
    _CAST_MAP = {
        str: cString,
        int: cInt,
        float: cFloat,
        bool: cBool,
        dict: cJson
    }

class CLibBinder:
    def __init__(self, libPath: str):
        self._libPath = libPath
        self.cdll = ctypes.CDLL(libPath)

    def native(self, argsType=[], returnType: _CTypeCast=_CTypeCast(), bindfuncName=None):
        def loader(func):
            funcName = bindfuncName
            if bindfuncName == None:
                funcName = func.__name__
            # 匹配并引用目标原型函数
            cFunc: ctypes._FuncPointer = getattr(self.cdll, funcName)

            # 经过测试 绝大部分性能开销是由跨语言调用以及数据拷贝参生的 PY层难以优化更多性能
            # @wraps(func)
            # def C_CALLBACK_CAST_FUNC(*args):
            #     if len(args) != len(argsType):
            #         raise RuntimeError("{} 参数数量不匹配: {}".format(funcName, args))
            #     cArgs = [ argsType[i].pyToC(v) for i, v in enumerate(args) ]
            #     ref = _CTypeCast.PyRef()
            #     cArgs.append(returnType.resCallBack(ref))
            #     cFunc(*cArgs)
            #     return ref.value

            # @wraps(func)
            # def C_DEFAULT_CAST_FUNC(*args):
            #     if len(args) != len(argsType):
            #         raise RuntimeError("{} 参数数量不匹配: {}".format(funcName, args))
            #     cArgs = [ argsType[i].pyToC(v) for i, v in enumerate(args) ]
            #     cFunc.restype = returnType.getRestype()
            #     return returnType.cToPy(cFunc(*cArgs))
            # if returnType.__class__.resCallBack is _CTypeCast.resCallBack:
            #     return C_DEFAULT_CAST_FUNC
            # return C_CALLBACK_CAST_FUNC

            @wraps(func)
            def C_CAST_FUNC(*args):
                if len(args) != len(argsType):
                    raise RuntimeError("{} 参数数量不匹配: {}".format(funcName, args))
                cArgs = [ argsType[i].pyToC(v) for i, v in enumerate(args) ]
                ref = _CTypeCast.PyRef()
                callBack = returnType.resCallBack(ref)
                # 复杂数据类型 采用回调方案接收数据
                if callBack:
                    cArgs.append(callBack)
                    cFunc(*cArgs)
                    return ref.value
                # 基础数据类型返回处理
                cFunc.restype = returnType.getRestype()
                return returnType.cToPy(cFunc(*cArgs))
            return C_CAST_FUNC
        return loader

    def py3AutoBinder(self, funcObj):
        typeDefine: dict[str, type] = funcObj.__annotations__
        returnType = None
        _returnType = typeDefine.get("return")
        if _returnType is None:
            returnType = _CTypeCast()
        else:
            returnType = C_CastType._CAST_MAP[_returnType]
        argsType = []
        for k, v in typeDefine.items():
            if k == "return":
                continue
            argsType.append(C_CastType._CAST_MAP[v])
        return self.native(argsType, returnType)(funcObj)